package com.beney.util.math;

/**
 * 抽象的数学等式生成工具.
 * 具体的生成值细节需要由不同的角色通过实现specifyVal(int)来完成，不同角色的区别主要是单目运算符
 * 通过单例模式获取一个特定的数学生成器
 *
 * @author Beney
 */
public abstract class AbstractMath {

    /**
     * 角色根据给定值包装出一个新值，主要是为了让不同角色组合单目运算符。
     * 例如，初中用户可以包装为√val，val²；高中用户可以包装为sinval，cosval，tanval
     *
     * @param val 可能与单目运算符组合的数值
     * @return 用户包装后的数值，可能组合了单目运算符，也可能没有
     */
    protected abstract String wrapVal(StringBuilder val);

    protected String wrapVal(int val) {
        StringBuilder sbVal = new StringBuilder(String.valueOf(val));
        return wrapVal(sbVal);
    }


    /**
     * 初始化至少当前角色需要满足的特定需求
     * 例如初中身份的就需要至少出现1个√或者²；高中身份就至少要出现一个三角函数
     */
    protected abstract void initSpecificNeed();


    /**
     * 获取随机生成的数学等式
     *
     * @param size 等式操作数的总数
     * @return 随机数学式
     */
    public StringBuilder createEquation(int size) {
        initSpecificNeed();     // 初始化当前级别的特殊需求
        return doCreate(size);  // 开始生成
    }

    /**
     * 操作数数目下限
     */
    private int minCount = 1;

    /**
     * 操作数数目上限
     */
    private int maxCount = 5;

    /**
     * 操作数值下限
     */
    private int loVal = 1;

    /**
     * 操作数值上限
     */
    private int hiVal = 100;

    /**
     * 双目运算符
     */
    protected String[] doubleOperands = {"+", "-", "*", "/"};


    public void setMinCount(int minCount) {
        if (minCount <= this.maxCount)
            this.minCount = minCount;
    }

    public void setMaxCount(int maxCount) {
        if (maxCount >= this.minCount)
            this.maxCount = maxCount;
    }

    public void setLoVal(int loVal) {
        if (loVal <= this.hiVal)
            this.loVal = loVal;
    }

    public void setHiVal(int hiVal) {
        if (hiVal >= this.loVal)
            this.hiVal = hiVal;
    }

    /**
     * 将数学式抽象成为二叉树的结构：分支结点保存的运算符，叶子结点保存操作数
     * 例如：2*(3+4)
     *   *
     *  / \
     * 2   +
     *    / \
     *   3   4
     */
    private class TreeNode {
        StringBuilder val;
        TreeNode left;
        TreeNode right;

        @Override
        public String toString() {
            if (left != null && right != null) {
                val.insert(0, left.toString());
                val.insert(0, '(');
                val.append(right.toString());
                val.append(')');
            }
            return val.toString();
        }

        public TreeNode(String val) {
            this.val = new StringBuilder(val);
            this.left = this.right = null;
        }

        public TreeNode(TreeNode left, String val, TreeNode right) {
            this.val = new StringBuilder(val);
            this.left = left;
            this.right = right;
        }
    }

    /**
     * 生成一棵表示等式的二叉树。
     * 操作数在叶子位置，分支结点都是操作符。
     *
     *
     * @param numberOfNodes 操作数的个数
     * @return 等式二叉树
     */
    private TreeNode createTree(int numberOfNodes) {
        if (numberOfNodes == 1)
            return new TreeNode(wrapVal(randomInt(loVal, hiVal)));
        TreeNode sub1 = createTree(numberOfNodes / 2);
        TreeNode sub2 = createTree((numberOfNodes + 1) / 2);
        int operandIt = (int) Math.floor(Math.random() * 4 + 0);

        if (Math.random() < 0.5)
            return new TreeNode(sub1, doubleOperands[operandIt], sub2);
        else
            return new TreeNode(sub2, doubleOperands[operandIt], sub1);
    }

    private int randomInt(int lo, int hi) {
        return (int) Math.floor(Math.random() * (hi - lo) + lo);
    }

    /**
     * 随机生成一个等式
     *
     * @param size 操作数个数
     * @return 等式
     */
    private StringBuilder doCreate(int size) {
        StringBuilder sb = new StringBuilder(createTree(size).toString());
        if (size > 1) { // 大于一个操作数的情况下会有多余的括号生成
            sb.deleteCharAt(0);
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append('='); // 添加等号
        return sb;
    }
}
